﻿var fs = require('fs');
var $ = window.$;
var iconv = require('iconv-lite');
var chardet = require('jschardet');
var path = require('path');
var fs = require('fs');
var logger = require('../../../util/logger')
var util = require('../../../util/index');
var notify = require('../../notify/ui');
var config = require('../../../core/config');

/**
 * jubeat memo导入
 */
var ret = {
    blankArr : ["□","－","口","－"],
    circledArr : ["①","②","③","④","⑤","⑥","⑦","⑧","⑨","⑩","⑪","⑫","⑬","⑭","⑮","⑯","⑰","⑱","⑲","⑳"],
    bigNum : ["１","２","３","４","５","６","７","８","９"],
    boogieArr : ["1","2","3","4","5","6","7","8","9","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u"],
    circledArrGt20 : ["｣ﾁ","｣ﾂ","｣ﾃ","｣ﾄ","｣ﾅ","｣ﾆ","｣ﾇ","｣ﾈ","｣ﾉ","｣ﾊ"],
	letterArrGt20 : ["Ａ","Ｂ","Ｃ","Ｄ","Ｅ","Ｆ","Ｇ","Ｈ","Ｉ,","Ｊ"],
    numArrGt20 : ["21","22","23","24","25","26","27","28","29","30"],
    boogieArrGt20 : ["l","m","n","o","p","q","r","s","t","u"],
    memoEncoding : null,
    lines : null,
    longHolds : [-1, -1, -1, -1,-1, -1, -1, -1,-1, -1, -1, -1,-1, -1, -1, -1],

    isBoogieFormat : function(line){
    	//一个合法的iBoogie谱面第一行必须有bpm数据 也就是()
    	return line.indexOf("(") > 0 && line.indexOf(")") > 0;
    },

    memo2Boogie : function(firstLineNum, lines){
    	for(var i=0;i<4;i++){
    		var line = lines[firstLineNum+i];
    		line = line.replace(/ /g,"");
    		//替换大于20的数字
    		for(var j=0;j<this.circledArrGt20.length;j++){
    			var regex1 = new RegExp(this.circledArrGt20[j], "g");
    			var regex2 = new RegExp(this.numArrGt20[j], "g");
				var regex3 = new RegExp(this.letterArrGt20[j], "g");
    			line = line.replace(regex1, this.boogieArrGt20[j]);
    			line = line.replace(regex2, this.boogieArrGt20[j]);
				line = line.replace(regex3, this.boogieArrGt20[j]);
    		}
    		//替换圈圈
    		for(var j=0;j<this.circledArr.length;j++){
    			var regex = new RegExp(this.circledArr[j], "g");
    			line = line.replace(regex, this.boogieArr[j]);
    		}
            //替换大写数字
            for(var j=0;j<this.bigNum.length;j++){
                var regex = new RegExp(this.bigNum[j], "g");
                line = line.replace(regex, j+1);
            }
            //替换分割线
    		line = line.replace(/(□|口)/g, "x");
    		line = line.replace(/(－|―)/g, "-");
            //替换长押符号
            line = line.replace(/(∨|Ｖ)/g, "v");
            line = line.replace(/∧/g, "^");
            line = line.replace(/＞/g, ">");
            line = line.replace(/＜/g, "<");
            line = line.replace(/｜/g, "L");//替换成半角竖线会死
    		lines[firstLineNum+i] = line;
            logger.log(">>" + line);
    	}

    },

    readLines : function(fn){
        var raw = fs.readFileSync(fn);
        var det = chardet.detect(raw);
        if(det.encoding && det.encoding.toLowerCase() == "utf-8" && det.confidence > 0.8){
            raw = fs.readFileSync(fn, {encoding:"utf8"});
            this.memoEncoding = "utf-8";
        }else if(det.encoding && det.encoding.toLowerCase() == "gb2312" && det.confidence > 0.8){
            raw = iconv.decode(raw, 'gb2312');
            this.memoEncoding = "gb2312";
        }else{
            raw = iconv.decode(raw, 'shift-jis');
            this.memoEncoding = "shift-jis";
        }
        return raw.split(/\n|\r\n/g);
    },

    arrElementInStr : function(arr, str){
        for(var i=0;i<arr.length;i++){
            if(str.indexOf(arr[i])>= 0){
                return i;
            }
        }
        return -1;
    },

    /*获得长押箭头的方向*/
    getHoldDirection : function(str){
        if(str == "v"){
            return "down";
        }else if(str == "^"){
            return "up";
        }else if(str == "<"){
            return "left";
        }else if(str == ">"){
            return "right";
        }
        return null;
    },


    /*判断本行有没有谱面字符1-20或者占位符*/
    isMemoCharInLine : function(line){
        return this.arrElementInStr(this.blankArr, line) >= 0 ||
            this.arrElementInStr(this.circledArr, line) >=0 || this.arrElementInStr(this.circledArrGt20, line) >=0
    },

    /*使用检测出的编码转换文字*/
    encodeStr : function(str, encoding){
        if(this.memoEncoding == "utf-8"){
            return str;
        }
        var buffer = iconv.encode(str , "binary");
        return iconv.decode(buffer, encoding || this.memoEncoding);
    },

    /*判断是否是双列谱面*/
    isTwoColSegment : function(firstLineIndex, lines){
        for(var i=0;i<4;i++){
            if(lines[firstLineIndex+i].indexOf("|") > 0){
                return true;
            }
        }
        return false;
    },

    linesToMatrix : function(firstLineNum, lines){
        var mat = [];
        for(var i=0;i<4;i++){
            var line = lines[firstLineNum + i];
            line = line.split("|")[0].trim();
            var arr = [];
            for(var j=0;j<4;j++){
                arr.push(line.charAt(j));
            }
            mat.push(arr);
        }
        return mat;
    },

    getNote : function(path){
        var ret = {
            meta : {
                song : {},
                mode : 4
            },
            time : [],
            note : [],
            extra : {
                samples : [],
                toggle : [2]
            }
        };
        this.ret = ret;
        var lines = this.readLines(path);
        this.lines = lines;
        var section = 0;//对应音乐上的小节
        var rawNotes = ret.note;//没有换算时间的note 因为note读取完之后可能需要整体平移一个小节
        var offset = null;
        var sample = null;
        var bpm = null;//当前小节的速度 因为只有读取完小节才知道本小节是4分还是6分
        var isTwoCol = false;//正在处理的是否是双列谱面
        var noBlankSegment = false;//HDD谱面默认第一个小节为空的 如果没有的话 要强制平移一个小节
        this.beatCount = 0;//全局的beat计数器 虽然每组都是四行 但是你不知道每组是几个beat
        var isBoogie = this.isBoogieFormat(lines[0]);
        if(isBoogie){
        	logger.log("this is an iBoogie map");
        }
        for(var i = 0; i < lines.length ; i ++) {
            var line = $.trim(lines[i]);
            //判断是不是参数
            if (line.indexOf("t=") === 0 || line.indexOf("BPM:") === 0) {
                if(line.indexOf("t=") === 0){
                    //jubeat memo的写法
                    bpm = parseInt(line.substring(2));
                }else if(line.indexOf("BPM:") === 0){
                    //cosmos memo的写法
                    bpm = $.trim(line.substring(4));
                    if(bpm.indexOf("-") > 0){
                        //这是AA-BB的变速标志 不能处理
                        continue;
                    }
                    bpm = parseInt(bpm);
                }
                if(ret.time.length){
                    ret.time.push({
                        beat:  [this.beatCount, 0, 4],
                        bpm: bpm
                    });
                }else{
                    ret.time.push({
                        beat:  [0,0,1],
                        bpm: bpm,
                        signature: 4,
                        measure: [0,0,4]
                    });
                }
                continue;
            } else if (line.indexOf("r=") === 0) {
                offset = parseInt(line.substring(2));
                continue;
            } else if (line.indexOf("m=") === 0) {
                sample = line.substring(2).replace(/"/g, "");
                //中文系统上能玩的数字谱 文件名部分用的gbk编码 但是也有一部分文件名用了shift-jis编码
                if(!this.addSample(path, sample, ret, "gbk")){
                    this.addSample(path, sample, ret, "shift_jis")
                }
                continue;
            } else if (line.indexOf("#") == 0) {
            	continue;
            } else if (line.indexOf("//") == 0) {
            	continue;
            } else if(/(Level|Notes):/.test(line)){
                continue;
            } else if(line.length >= 4 && ((!isBoogie && this.isPossibleMemoLine(line)) || (isBoogie && this.isPossibleBoogieLine(line)))) {
            	if(!isBoogie){
            		this.memo2Boogie(i, lines);
            	}
            	sectionStart = true;
                isTwoCol = this.isTwoColSegment(i, lines);
                var notes = [];
                if (isTwoCol) {
                    var result = this.parseTwoColSection(section, i, lines);
                    if(!result){
                    	break;
                    }
                    notes = result.notes;
                    i += 3;
                } else {
                    notes = this.parseSingleColSection(section, i, lines);
                    i += 3;
                }
                if (section == 0 && notes.length > 0) {
                    noBlankSegment = true;
                }
                for (var k = 0; k < notes.length; k++) {
                    rawNotes.push(notes[k]);
                    notes[k].id = rawNotes.length;
                }
            }
        }
        //对没有第一空白小节的谱面强制平移4拍
        if(noBlankSegment){
        	logger.log("first segment isn't blank,move 4 beat");
            for(var i = 0; i < rawNotes.length ; i ++){
                if(!rawNotes[i].isBgm){
                    rawNotes[i].beat[0] += 4;
                    if(rawNotes[i].endbeat){
                        rawNotes[i].endbeat[0] += 4;
                    }
                }
            }
            //如果有变速 也要平移4拍
            if(ret.time.length > 1){
                for(var i=1;i<ret.time.length;i++){
                    ret.time[i].beat[0] +=4;
                }
            }
        }
        //调整音乐的延时
        if(offset){
            ret.note[0].offset = offset * -1 - 80;  //-80魔法值
        }
        if(ret.note.length <= 1){
            notify.addNotify({msg : config.i18n("ubt07")});
        }
        this.memoEncoding = null;
        this.lines = null;
        return ret;
    },

    addSample : function(chartPath, sample, chart, encoding){
        sample = this.encodeStr(sample, encoding);
        var fn = path.dirname(chartPath) + "/" + sample;
        if(!fs.existsSync(fn)){
            return false;
        }
        var note = {
            sound : sample,
            vol : 30,
            type : 1,
            beat: [0, 0, 4],
            column : 0,
            isBgm : true
        };
        chart.note.push(note);
        chart.extra.samples.push({
            name : sample
        });
        return true;
    },

    isPossibleMemoLine : function(str){
        return this.isMemoCharInLine(str);
    },

    isPossibleBoogieLine : function(str){
    	return str.indexOf("|")>0 || str.trim().length == 4;
    },

    parseSingleColSection : function(section, firstLineNum, lines){
        var notes = [];
        for(var j=0;j<4;j++){
            var line = lines[firstLineNum+j];
            for(var i = 0;i < 4; i++){
                var code = line[i];
                var index = this.arrElementInStr(this.circledArr, code);
                if(index != -1){
                    var beat = Math.floor((index / 4));
                    var subBeat = index % 4 + 1;
                    var note = {
                        beat : [section*4 + beat, subBeat, 4],
                        index : j * 4 + i
                    };
                    notes.push(note);
                }
            }
        }
        return notes;
    },

    parseHoldNotes : function(section, firstLineNum, lines){
        //首先在4行中查找所有的长押点
        var mat = this.linesToMatrix(firstLineNum, lines);
        for(var i=0;i<4;i++){
            for(var j=0;j<4;j++){
                var c = mat[i][j];
                var direction = this.getHoldDirection(c);
                if(!direction){
                    continue;
                }
                //查找具体按压的位置
                var tmp,idx,num;
                if(direction == "up"){
                    for(tmp = i;tmp>=0;tmp--){
                        num = this.arrElementInStr(this.boogieArr, mat[tmp][j]);
                        if(num != -1){
                            idx = tmp*4 + j;
                            break;
                        }
                    }
                }else if(direction == "down"){
                    for(tmp = i;tmp<4;tmp++){
                        num = this.arrElementInStr(this.boogieArr, mat[tmp][j]);
                        if(num != -1){
                            idx = tmp*4 + j;
                            break;
                        }
                    }
                }else if(direction == "right"){
                    for(tmp = j;tmp<4;tmp++){
                        num = this.arrElementInStr(this.boogieArr, mat[i][tmp]);
                        if(num != -1){
                            idx = i*4 + tmp;
                            break;
                        }
                    }
                }else if(direction == "left"){
                    for(tmp = j;tmp>=0;tmp--){
                        num = this.arrElementInStr(this.boogieArr, mat[i][tmp])
                        if(num != -1){
                            idx = i*4 + tmp;
                            break;
                        }
                    }
                }
                this.longHolds[idx] = {
                    index : idx,
                    endindex : i*4 + j,
                    head : true//标志位 第一次加开始时间 然后拿掉标志位添加结束时间
                };
            }
        }
    },

    parseBoogieTiming : function(line,section,ln){
    	var timing = []
        for(var i=0;i<32;i++){
        	timing.push({});
        }
        var tmp = 0;
        var ctrlIdx = 0;
        var offsetStart = false;
        var bpmStart = false;
        var offsetStr = "";
        var bpmStr = "";
        while(tmp < line.length){
        	var c = line.charAt(tmp);
        	tmp ++;
        	if(c == "("){
        		bpmStart = true;
        	}else if(c == "["){
        		offsetStart = true;
        	}else if(c == ")"){
        		timing[ctrlIdx].bpm = +bpmStr;
        	}else if(c =="]"){
        		timing[ctrlIdx].offset = +offsetStr;
        	}else{
        		if(offsetStart){
        			offsetStr += c;
        		}else if(bpmStart){
        			bpmStr += c;
        		}else{
        			ctrlIdx += 1;
        		}
        	}
        }
        line = line.replace(/\[.*?\]/g, "");
        line = line.replace(/\(.*?\)/g, "");
        var divide = line.length;
        var ret = this.ret;
        for(var i=0;i<divide;i++){
        	var msg = timing[i];
        	if(msg.bpm){
        		if(ret.time.length){
                    ret.time.push({
                        beat:  [section * 4,i,divide],
                        bpm: msg.bpm
                    });
                }else{
                    ret.time.push({
                        beat:  [0,0,1],
                        bpm: msg.bpm,
                        signature: 4,
                        measure: [0,0,4]
                    });
                }
        	}
        }
        return line;
    },

    parseTwoColSection : function(section, firstLineNum, lines){
        var notes = [];
        var mapping = [-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1];
        //先处理长押音符
        this.parseHoldNotes(section, firstLineNum, lines);
        //处理左侧的部分 产生计时编号->note组的映射
        for(var j=0;j<4;j++){
        	if(firstLineNum+j>=lines.length){
        		notify.addNotify({msg : util.formatStr(config.i18n("ubt03"), [section + 1])});
        		return;
        	}
            var line = lines[firstLineNum+j];
            if(line.indexOf("|")>0){
                line = line.split("|")[0].trim();
            }
            for(var i = 0;i < 4; i++){
                var index = j*4+i;
                if(i >= line.length){
                    notify.addNotify({msg : util.formatStr(config.i18n("ubt04"), [section + 1, i + 1])});
                	return;
                }
                var num = this.arrElementInStr(this.boogieArr, line[i]);
                if(num >=0){
                    if(mapping[num] == -1){
                        mapping[num] = [];
                    }
                    var note = {
                        index : index
                    };
                    //如果映射的位置有长押没处理完 则这个映射只处理时间不产生新note
                    //也就是必须要求长押按压点的下一次出现一定是按压的结束时间
                    if(this.longHolds[index] != -1){
                        note.onlyMapping = true;
                    }
                    mapping[num].push(note);
                }
            }
        }
        //处理音符的时间
        for(var j=0;j<4;j++) {
            var line = lines[firstLineNum + j];
            var sepLine = line.indexOf("|");
            if(sepLine<4){
                continue;
            }
            line = line.substring(sepLine+1).replace(/\|/g, "").trim();
            
            //处理掉所有的控制信息 这里是iboogie用的 
            var line = this.parseBoogieTiming(line, section, j);
            var divide = line.length;

            for(var i = 0;i < divide; i++){
                var num = this.arrElementInStr(this.boogieArr, line[i]);
                if(num >= 0){
                    var beat = [this.beatCount , i, divide];
                    var longHoldIndexArr = [];
                    for(var k=0;k<mapping[num].length;k++){
                    	if(!mapping[num]){
                            notify.addNotify({msg : util.formatStr(config.i18n("ubt05"), [section + 1, num + 1])});
			                return;
                    	}
                        var m = mapping[num][k];
                        //注意这里的beat不能指向同一个引用 否则在整体平移时会掉坑
                        m.beat = [beat[0], beat[1], beat[2]];
                        if(!m.onlyMapping){
                            notes.push(m);
                            continue;
                        }else{
                            longHoldIndexArr.push(m.index);
                        }
                    }
                    //这里注意一个时间号也可能对多个长押点
                    for(var n=0;n<longHoldIndexArr.length;n++){
                        var idx = longHoldIndexArr[n];
                        var lh = this.longHolds[idx];
                        if(lh != -1){
                            if(lh.head){
                                lh.beat = [beat[0], beat[1], beat[2]];
                                delete lh.head
                            }else{
                                lh.endbeat = [beat[0], beat[1], beat[2]];
                                notes.push(lh);
                                this.longHolds[idx] = -1;
                            }
                        }
                    }
                }

            }
            this.beatCount += 1;
        }
        logger.log("import section : " + (section+1) + ",notes=" + notes.length);
        return {notes : notes};
    }
};

module.exports = ret;